package cn.lingyangwl.agile.gateway.filter;

import cn.lingyangwl.agile.gateway.body.*;
import cn.lingyangwl.agile.gateway.model.*;
import cn.lingyangwl.agile.gateway.service.*;
import cn.lingyangwl.agile.gateway.sign.core.*;
import cn.lingyangwl.agile.gateway.sign.model.*;
import cn.lingyangwl.agile.model.constants.RequestCons.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.framework.tool.core.*;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * 获取请求以及响应数据
 * DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); +  gzip解码方可解决乱码情况
 */
@Component
@Slf4j
public class ResponseBodyUpdateGlobalFilter implements GlobalFilter, Ordered {

    @Resource
    private FileService fileService;
    @Resource
    private StatisticService statisticService;

    @Override
    public int getOrder() {
        // 控制在NettyWriteResponseFilter后执行
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
    }
 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        ServerHttpResponse originalResponse = exchange.getResponse();

        String url = serverHttpRequest.getPath().toString();

        //请求参数，post从请求里获取请求体 HttpMethod.POST.equals(method) ?
        GatewayContext gatewayContext = GatewayContext.getContext(exchange);
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        SignContext signContext = new SignContext(gatewayContext);
        IApiSign signInstance = AbstractApiSign.getInstance(signContext);

        // 是否校验请求签名
        if (Objects.nonNull(signInstance) && signInstance.isVerifySign(signContext)) {
            // 开始校验签名
            signInstance.verifySign(signContext);
        }

        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            @SuppressWarnings("unchecked")
            public @NotNull Mono<Void> writeWith(@NotNull Publisher<? extends DataBuffer> body) {
                if (originalResponse.getHeaders().containsKey("Content-Disposition")) {
                    log.debug("请求 [{}] 为下载文件操,判断依据是响应中携带 Content-Disposition 请求头", url);
                } else if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        // 解决返回体分段传输
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);

                        byte[] uncompressContent = uncompress(content);
                        boolean isUncompress = true;
                        if (uncompressContent == null) {
                            uncompressContent = content;
                            isUncompress = false;
                        }
                        DataBufferUtils.release(join);
                        String responseData = new String(uncompressContent, StandardCharsets.UTF_8);

                        // 获取响应的租户id
                        String respTenantId = originalResponse.getHeaders().getFirst(Headers.TENANT_ID);
                        if (StringUtils.isNotEmpty(respTenantId)) {
                            originalResponse.getHeaders().remove(Headers.TENANT_ID);
                        }
                        // 替换上传的文件存储路径
                        responseData = fileService.virtualToRealBaseUrl(serverHttpRequest, respTenantId, responseData);

                        JSONObject jsonObject;

                        if ((jsonObject = getJSONObject(responseData)) != null && Objects.nonNull(signInstance)
                                && !signInstance.isGenRespSign(signContext)) {
                            signInstance.genRespSign(signContext, jsonObject);
                            responseData = jsonObject.toJSONString();
                            log.info("time: {} ms, data: {}", System.currentTimeMillis() - gatewayContext.getStartTime(), JSON.toJSONString(gatewayContext));
                        }

                        // 统计访问日志
                        boolean isFail = originalResponse.getStatusCode() != HttpStatus.OK ||
                                (Objects.nonNull(jsonObject) && !"1".equals(jsonObject.getString("code")));

                        try {
                            TenantContext.get().setTenantId(gatewayContext.getTenantId())
                                    .setMainTenantId(gatewayContext.getMainTenantId());

                            DayDimStatReq statReq = new DayDimStatReq();
                            statReq.terminal(serverHttpRequest);
                            statReq.setAccessSuccessCount(isFail ? null : 1);
                            statReq.setAccessFailCount(isFail ? 1 : null);
                            statisticService.sendMsg(statReq);

                        } finally {
                            TenantContext.clear();
                        }

                        byte[] uppedContent;
                        if (isUncompress) {
                            uppedContent = compress(responseData,"utf-8");
                        } else {
                            uppedContent = new String(responseData.getBytes(),StandardCharsets.UTF_8).getBytes();
                        }

                        originalResponse.getHeaders().setContentLength(uppedContent.length);
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };

        // 设置请求头
        ServerHttpRequest.Builder mutate = exchange.getRequest().mutate();
        Map<String, String> addRequestHeaders = gatewayContext.getAddRequestHeaders();
        addRequestHeaders.forEach(mutate::header);

        // replace response with decorator
        return chain.filter(exchange.mutate().request(mutate.build()).response(decoratedResponse).build());
    }


    /**
     * 解码 gzip,否则依旧会出现乱码情况
     */
    public static byte[] uncompress(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        try {
            GZIPInputStream ungzip = new GZIPInputStream(in);
            byte[] buffer = new byte[4096];
            int n;
            while ((n = ungzip.read(buffer)) >= 0) {
                out.write(buffer, 0, n);
            }
        } catch (Exception e) {
            return null;
        }

        return out.toByteArray();
    }

    /**
     * 编码 gzip
     */
    public static byte[] compress(String str, String encoding) {
        if (str == null || str.length() == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPOutputStream gzip;
        try {
            gzip = new GZIPOutputStream(out);
            gzip.write(str.getBytes(encoding));
            gzip.close();
        } catch (IOException e) {
            log.debug("gzip compress error.", e);
            return new String(str.getBytes(),StandardCharsets.UTF_8).getBytes();
        }
        return out.toByteArray();
    }

    /**
     * 获取json对象
     * @param str 字符串
     */
    public static JSONObject getJSONObject(String str) {
        try {
            return JSONObject.parseObject(str);
        } catch (Exception e) {
            return null;
        }
    }
}